/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.editor;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.resource.DeviceResourceException;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
import org.eclipse.ui.part.EditorActionBarContributor;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.DefaultRangeIndicator;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.IEditorStatusLine;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.ITextEditorExtension2;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.python.pydev.builder.PydevMarkerUtils;
import org.python.pydev.builder.PydevMarkerUtils.MarkerInfo;
import org.python.pydev.changed_lines.ChangedLinesComputer;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.FileUtilsFileBuffer;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.IDefinition;
import org.python.pydev.core.IGrammarVersionProvider;
import org.python.pydev.core.IIndentPrefs;
import org.python.pydev.core.IModulesManager;
import org.python.pydev.core.IPyEdit;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.NotConfiguredInterpreterException;
import org.python.pydev.core.OrderedSet;
import org.python.pydev.core.Tuple3;
import org.python.pydev.core.bundle.ImageCache;
import org.python.pydev.core.callbacks.CallbackWithListeners;
import org.python.pydev.core.callbacks.ICallbackWithListeners;
import org.python.pydev.core.docutils.PyPartitionScanner;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.SyntaxErrorException;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.parser.ISimpleNode;
import org.python.pydev.editor.actions.FirstCharAction;
import org.python.pydev.editor.actions.OfflineAction;
import org.python.pydev.editor.actions.OfflineActionTarget;
import org.python.pydev.editor.actions.PyAction;
import org.python.pydev.editor.actions.PyBackspace;
import org.python.pydev.editor.actions.PyFormatStd;
import org.python.pydev.editor.actions.PyMoveLineDownAction;
import org.python.pydev.editor.actions.PyMoveLineUpAction;
import org.python.pydev.editor.actions.PyOpenAction;
import org.python.pydev.editor.actions.PyPeerLinker;
import org.python.pydev.editor.autoedit.DefaultIndentPrefs;
import org.python.pydev.editor.autoedit.PyAutoIndentStrategy;
import org.python.pydev.editor.codecompletion.revisited.CompletionCache;
import org.python.pydev.editor.codecompletion.revisited.CompletionStateFactory;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.editor.codecompletion.revisited.visitors.Definition;
import org.python.pydev.editor.codecompletion.shell.AbstractShell;
import org.python.pydev.editor.codefolding.CodeFoldingSetter;
import org.python.pydev.editor.codefolding.PyEditProjection;
import org.python.pydev.editor.codefolding.PySourceViewer;
import org.python.pydev.editor.model.IModelListener;
import org.python.pydev.editor.model.ItemPointer;
import org.python.pydev.editor.preferences.PydevEditorPrefs;
import org.python.pydev.editor.refactoring.PyRefactoringFindDefinition;
import org.python.pydev.editor.scripting.PyEditScripting;
import org.python.pydev.editorinput.PyOpenEditor;
import org.python.pydev.editorinput.PydevFileEditorInput;
import org.python.pydev.logging.ping.ILogPing;
import org.python.pydev.outline.PyOutlinePage;
import org.python.pydev.parser.ErrorDescription;
import org.python.pydev.parser.PyParser;
import org.python.pydev.parser.PyParserManager;
import org.python.pydev.parser.fastparser.FastParser;
import org.python.pydev.parser.jython.ParseException;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.plugin.preferences.PyCodeFormatterPage;
import org.python.pydev.plugin.preferences.PydevPrefs;
import org.python.pydev.ui.ColorAndStyleCache;
import org.python.pydev.ui.UIConstants;
import org.python.pydev.ui.filetypes.FileTypesPreferencesPage;
import com.aptana.shared_core.structure.Tuple;
import com.aptana.shared_core.utils.Reflection;
/**
* The TextWidget.
*
* <p>
* Ties together all the main classes in this plugin.
* <li>The {@link org.python.pydev.editor.PyEditConfiguration PyEditConfiguration}does preliminary partitioning.
* <li>The {@link org.python.pydev.parser.PyParser PyParser}does a lazy validating python parse.
* <li>The {@link org.python.pydev.outline.PyOutlinePage PyOutlinePage}shows the outline
*
* <p>
* Listens to the parser's events, and displays error markers from the parser.
*
* <p>
* General notes:
* <p>
* TextWidget creates SourceViewer, an SWT control
*
* @see <a href="http://dev.eclipse.org/newslists/news.eclipse.tools/msg61594.html">This eclipse article was an inspiration </a>
*
*/
public class PyEdit extends PyEditProjection implements IPyEdit, IGrammarVersionProvider,
IPySyntaxHighlightingAndCodeCompletionEditor {
static {
ParseException.verboseExceptions = true;
}
public static final String PY_EDIT_CONTEXT = "#PyEditContext";
static public final String EDITOR_ID = "org.python.pydev.editor.PythonEditor";
static public final String ACTION_OPEN = "OpenEditor";
static private final Set<PyEdit> currentlyOpenedEditors = new HashSet<PyEdit>();
static private final Object currentlyOpenedEditorsLock = new Object();
/** color cache */
private ColorAndStyleCache colorCache;
// Listener waits for tab/spaces preferences that affect sourceViewer
private IPropertyChangeListener prefListener;
/** need it to support GUESS_TAB_SUBSTITUTION preference */
private PyAutoIndentStrategy indentStrategy;
/** need to hold onto it to support indentPrefix change through preferences */
private PyEditConfiguration editConfiguration;
public PyEditConfiguration getEditConfiguration() {
return editConfiguration;
}
public ISourceViewer getEditorSourceViewer() {
return super.getSourceViewer();
}
public IAnnotationModel getAnnotationModel() {
final IDocumentProvider documentProvider = getDocumentProvider();
if (documentProvider == null) {
return null;
}
return documentProvider.getAnnotationModel(getEditorInput());
}
public ColorAndStyleCache getColorCache() {
return colorCache;
}
public PySelection createPySelection() {
return new PySelection(this);
}
/**
* AST that created python model
*/
private volatile SimpleNode ast;
private volatile long astModificationTimeStamp = -1;
/**
* The last parsing error description we got.
*/
private volatile ErrorDescription errorDescription;
/** listeners that get notified of model changes */
List<IModelListener> modelListeners;
// ---------------------------- listeners stuff
/**
* Those are the ones that register with the PYDEV_PYEDIT_LISTENER extension point
*/
private static List<IPyEditListener> editListeners;
/**
* Those are the ones that register at runtime (not through extensions points).
*/
private final Collection<IPyEditListener> registeredEditListeners = new OrderedSet<IPyEditListener>();
/**
* This is the scripting engine that is binded to this interpreter.
*/
private PyEditScripting pyEditScripting;
/**
* Lock for initialization sync
*/
private Object lock = new Object();
public final ICallbackWithListeners<Composite> onCreatePartControl = new CallbackWithListeners<Composite>();
public final ICallbackWithListeners<ISourceViewer> onAfterCreatePartControl = new CallbackWithListeners<ISourceViewer>();
public final ICallbackWithListeners<PyEdit> onCreateActions = new CallbackWithListeners<PyEdit>();
public final ICallbackWithListeners<Class<?>> onGetAdapter = new CallbackWithListeners<Class<?>>();
public final ICallbackWithListeners<LineNumberRulerColumn> onInitializeLineNumberRulerColumn = new CallbackWithListeners<LineNumberRulerColumn>();
public final ICallbackWithListeners<?> onDispose = new CallbackWithListeners<Object>();
public final ICallbackWithListeners<PropertyChangeEvent> onHandlePreferenceStoreChanged = new CallbackWithListeners<PropertyChangeEvent>();
public final ICallbackWithListeners<PySourceViewer> onCreateSourceViewer = new CallbackWithListeners<PySourceViewer>();
public void addPyeditListener(IPyEditListener listener) {
synchronized (registeredEditListeners) {
registeredEditListeners.add(listener);
}
}
public void removePyeditListener(IPyEditListener listener) {
synchronized (registeredEditListeners) {
registeredEditListeners.remove(listener);
}
}
public ISourceViewer getISourceViewer() {
return getSourceViewer();
}
public IVerticalRuler getIVerticalRuler() {
return getVerticalRuler();
}
public List<IPyEditListener> getAllListeners() {
return getAllListeners(true);
}
@Override
protected void initializeLineNumberRulerColumn(LineNumberRulerColumn rulerColumn) {
super.initializeLineNumberRulerColumn(rulerColumn);
this.onInitializeLineNumberRulerColumn.call(rulerColumn);
}
@Override
protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
super.handlePreferenceStoreChanged(event);
this.onHandlePreferenceStoreChanged.call(event);
}
public List<IPyEditListener> getAllListeners(boolean waitInit) {
if (waitInit) {
while (initFinished == false) {
synchronized (getLock()) {
try {
if (initFinished == false) {
getLock().wait();
}
} catch (Exception e) {
//ignore
Log.log(e);
}
}
}
}
ArrayList<IPyEditListener> listeners = new ArrayList<IPyEditListener>();
if (editListeners != null) {
listeners.addAll(editListeners); //no need to sync because editListeners is read-only
}
synchronized (registeredEditListeners) {
listeners.addAll(registeredEditListeners);
}
return listeners;
}
private Object getLock() {
return lock;
}
@Override
public void createPartControl(Composite parent) {
Composite newParent = (Composite) this.onCreatePartControl.call(parent);
if (newParent != null) {
parent = newParent;
}
super.createPartControl(parent);
this.onAfterCreatePartControl.call(getSourceViewer());
}
/**
* This map may be used by clients to store info regarding this editor.
*
* Clients should be careful so that this key is unique and does not conflict with other
* plugins.
*
* This is not enforced.
*
* The suggestion is that the cache key is always preceded by the class name that will use it.
*/
public Map<String, Object> cache = new HashMap<String, Object>();
public Map<String, Object> getCache() {
return cache;
}
/**
* Indicates whether the init was already finished
*/
protected boolean initFinished = false;
private final PyEditNotifier notifier;
private boolean disposed = false;
public boolean isDisposed() {
return disposed;
}
/**
* Anyone may register to know when PyEdits are created.
*/
public static final ICallbackWithListeners<PyEdit> onPyEditCreated = new CallbackWithListeners<PyEdit>();
// ---------------------------- end listeners stuff
@SuppressWarnings("unchecked")
public PyEdit() {
super();
synchronized (currentlyOpenedEditorsLock) {
currentlyOpenedEditors.add(this);
}
notifier = new PyEditNotifier(this);
try {
onPyEditCreated.call(this);
} catch (Throwable e) {
Log.log(e);
}
try {
//initialize the 'save' listeners of PyEdit
if (editListeners == null) {
editListeners = ExtensionHelper.getParticipants(ExtensionHelper.PYDEV_PYEDIT_LISTENER);
}
notifier.notifyEditorCreated();
modelListeners = new ArrayList<IModelListener>();
colorCache = new ColorAndStyleCache(PydevPrefs.getChainedPrefStore());
editConfiguration = new PyEditConfiguration(colorCache, this, PydevPrefs.getChainedPrefStore());
setSourceViewerConfiguration(editConfiguration);
indentStrategy = editConfiguration.getPyAutoIndentStrategy();
setRangeIndicator(new DefaultRangeIndicator()); // enables standard
// vertical ruler
//Added to set the code folding.
CodeFoldingSetter codeFoldingSetter = new CodeFoldingSetter(this);
this.addModelListener(codeFoldingSetter);
this.addPropertyListener(codeFoldingSetter);
} catch (Throwable e) {
Log.log(e);
}
}
/**
* Overridden so that we can:
* - Set up the cursor listener (notifies changes in the cursor position)
* - Make the backspace handling in a way that the incremental find works (note: having the listener in the
* textWidget does not work for that, as the event used in the IncrementalFindTarget is not the same event
* that goes to the textWidget).
*/
@Override
protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
PySourceViewer viewer = (PySourceViewer) super.createSourceViewer(parent, ruler, styles);
//add a cursor listener
StyledText textWidget = viewer.getTextWidget();
PyEditCursorListener cursorListener = new PyEditCursorListener();
textWidget.addMouseListener(cursorListener);
textWidget.addKeyListener(cursorListener);
viewer.appendVerifyKeyListener(PyPeerLinker.createVerifyKeyListener(viewer));
viewer.appendVerifyKeyListener(PyBackspace.createVerifyKeyListener(viewer, this));
VerifyKeyListener createVerifyKeyListener = FirstCharAction.createVerifyKeyListener(viewer, this.getSite(),
false);
if (createVerifyKeyListener != null) {
viewer.appendVerifyKeyListener(createVerifyKeyListener);
}
this.onCreateSourceViewer.call(viewer);
return viewer;
}
/**
* Class to notify clients that the cursor position changed.
*/
private class PyEditCursorListener implements MouseListener, KeyListener {
private int lastOffset = -1;
/**
* Notifies clients about a change in the cursor position.
*/
private void notifyCursorPositionChanged() {
if (!initFinished) {
return;
}
PySelection ps = new PySelection(PyEdit.this);
for (IPyEditListener listener : getAllListeners()) {
try {
if (listener instanceof IPyEditListener2) {
((IPyEditListener2) listener).handleCursorPositionChanged(PyEdit.this, ps);
}
} catch (Throwable e) {
//must not fail
Log.log(e);
}
}
}
public void mouseDoubleClick(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
}
/**
* notify when the user makes a click
*/
public void mouseUp(MouseEvent e) {
lastOffset = getOffset();
notifyCursorPositionChanged();
}
public void keyPressed(KeyEvent e) {
}
private int getOffset() {
return ((ITextSelection) PyEdit.this.getSelectionProvider().getSelection()).getOffset();
}
/**
* Notify when the user makes an arrow movement which actually changes the cursor position (because
* while doing code-completion it could make that notification when the cursor was changed in the
* dialog -- even if it didn't affect the cursor position).
*/
public void keyReleased(KeyEvent e) {
if (e.character == '\0') {
switch (e.keyCode) {
case SWT.ARROW_DOWN:
case SWT.ARROW_UP:
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
case SWT.HOME:
case SWT.END:
case SWT.PAGE_UP:
case SWT.PAGE_DOWN:
int offset = getOffset();
if (offset != lastOffset) {
notifyCursorPositionChanged();
lastOffset = offset;
}
default:
return;
}
}
}
}
/**
* Sets the forceTabs preference for auto-indentation.
*
* <p>
* This is the preference that overrides "use spaces" preference when file contains tabs (like mine do).
* <p>
* If the first indented line starts with a tab, then tabs override spaces.
*/
public void resetForceTabs() {
IDocument doc = getDocumentProvider().getDocument(getEditorInput());
if (doc == null) {
return;
}
if (!PydevPrefs.getPreferences().getBoolean(PydevEditorPrefs.GUESS_TAB_SUBSTITUTION)) {
getIndentPrefs().setForceTabs(false);
return;
}
int lines = doc.getNumberOfLines();
boolean forceTabs = false;
int i = 0;
// look for the first line that starts with ' ', or '\t'
while (i < lines) {
try {
IRegion r = doc.getLineInformation(i);
String text = doc.get(r.getOffset(), r.getLength());
if (text != null)
if (text.startsWith("\t")) {
forceTabs = true;
break;
} else if (text.startsWith(" ")) {
forceTabs = false;
break;
}
} catch (BadLocationException e) {
Log.log(IStatus.ERROR, "Unexpected error forcing tabs", e);
break;
}
i++;
}
getIndentPrefs().setForceTabs(forceTabs);
editConfiguration.resetIndentPrefixes();
// display a message in the status line
if (forceTabs) {
updateForceTabsMessage();
}
}
public void updateForceTabsMessage() {
boolean forceTabs = getIndentPrefs().getForceTabs();
ImageCache imageCache = PydevPlugin.getImageCache();
ImageDescriptor desc;
if (forceTabs) {
desc = imageCache.getDescriptor(UIConstants.FORCE_TABS_ACTIVE);
} else {
desc = imageCache.getDescriptor(UIConstants.FORCE_TABS_INACTIVE);
}
IEditorStatusLine statusLine = (IEditorStatusLine) getAdapter(IEditorStatusLine.class);
if (statusLine != null) {
statusLine.setMessage(false, forceTabs ? "Forcing tabs" : "Not forcing tabs.", desc.createImage());
}
}
/**
* @return the indentation preferences. Any action writing something should use this one in the editor because
* we want to make sure that the indent must be the one bounded to this editor (because tabs may be forced in a given
* editor, even if does not match the global settings).
*/
public IIndentPrefs getIndentPrefs() {
return indentStrategy.getIndentPrefs();
}
public PyAutoIndentStrategy getAutoEditStrategy() {
return indentStrategy;
}
//Just making interface public
public void resetIndentPrefixes() {
super.updateIndentPrefixes();
}
/**
* Overriden becaus pydev already handles spaces -> tabs
*/
@Override
protected void installTabsToSpacesConverter() {
//Do nothing (pydev already handles that)
updateIndentPrefixes();
}
/**
* Overriden becaus pydev already handles spaces -> tabs
*/
@Override
protected void uninstallTabsToSpacesConverter() {
//Do nothing (pydev already handles that)
updateIndentPrefixes();
}
/**
* Initializes everyone that needs document access
*
*/
public void init(final IEditorSite site, final IEditorInput input) throws PartInitException {
try {
super.init(site, input);
final IDocument document = getDocument(input);
// check the document partitioner (sanity check / fix)
PyPartitionScanner.checkPartitionScanner(document);
// Also adds Python nature to the project.
// The reason this is done here is because I want to assign python
// nature automatically to any project that has active python files.
final IPythonNature nature = PythonNature.addNature(input);
//we also want to initialize our shells...
//we use 2: one for refactoring and one for code completion.
Thread thread2 = new Thread() {
public void run() {
try {
try {
AbstractShell.getServerShell(nature, AbstractShell.COMPLETION_SHELL);
} catch (RuntimeException e1) {
}
} catch (Exception e) {
}
}
};
thread2.setName("Shell starter");
thread2.start();
// listen to changes in TAB_WIDTH preference
prefListener = createPrefChangeListener(this);
resetForceTabs();
PydevPrefs.getChainedPrefStore().addPropertyChangeListener(prefListener);
Runnable runnable = new Runnable() {
public void run() {
//let's do that in a thread, so that we don't have any delays in setting up the editor
pyEditScripting = new PyEditScripting();
addPyeditListener(pyEditScripting);
initFinished = true;
synchronized (getLock()) {
getLock().notifyAll();
}
}
};
Thread thread = new Thread(runnable);
thread.setName("PyEdit initializer");
thread.start();
} catch (Throwable e) {
//never fail in the init
Log.log(e);
}
}
public static IPropertyChangeListener createPrefChangeListener(
final IPySyntaxHighlightingAndCodeCompletionEditor editor) {
return new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
String property = event.getProperty();
//tab width
if (property.equals(PydevEditorPrefs.TAB_WIDTH)) {
ISourceViewer sourceViewer = editor.getEditorSourceViewer();
if (sourceViewer == null) {
return;
}
editor.getIndentPrefs().regenerateIndentString();
sourceViewer.getTextWidget().setTabs(DefaultIndentPrefs.getStaticTabWidth());
editor.resetIndentPrefixes();
} else if (property.equals(PydevEditorPrefs.SUBSTITUTE_TABS)) {
editor.getIndentPrefs().regenerateIndentString();
editor.resetIndentPrefixes();
//auto adjust for file tabs
} else if (property.equals(PydevEditorPrefs.GUESS_TAB_SUBSTITUTION)) {
editor.resetForceTabs();
editor.resetIndentPrefixes();
//colors and styles
} else if (ColorAndStyleCache.isColorOrStyleProperty(property)) {
editor.getColorCache().reloadNamedColor(property); //all reference this cache
editor.getEditConfiguration().updateSyntaxColorAndStyle(); //the style needs no reloading
editor.getEditorSourceViewer().invalidateTextPresentation();
}
}
};
}
//Deal with notifying the user that the opened file is invalid -----------------------------------------------------
private static final String INVALID_MODULE_MARKER_TYPE = "org.python.pydev.invalidpythonfilemarker";
private void checkAddInvalidModuleNameMarker(IDocument doc, IFile file) {
try {
String name = file.getName();
int i = name.lastIndexOf('.');
if (i > 0) {
String modName = name.substring(0, i);
if (!PythonPathHelper.isValidModuleLastPart(modName)) {
addInvalidModuleMarker(doc, file, "Invalid name for Python module: " + modName
+ " (it'll not be analyzed)");
return;
} else if (!PythonPathHelper.isValidSourceFile(name)) {
addInvalidModuleMarker(doc, file, "Module: " + modName
+ " does not have a valid Python extension (it'll not be analyzed).");
return;
}
}
//if it still hasn't returned, remove any existing marker (i.e.: rename operation)
removeInvalidModuleMarkers(file);
} catch (Exception e) {
Log.log(e);
}
}
private void removeInvalidModuleMarkers(IFile file) {
try {
if (file.exists()) {
file.deleteMarkers(INVALID_MODULE_MARKER_TYPE, true, IResource.DEPTH_ZERO);
}
} catch (Exception e) {
Log.log(e);
}
}
private void addInvalidModuleMarker(IDocument doc, IFile fileAdapter, String msg) {
MarkerInfo markerInfo = new PydevMarkerUtils.MarkerInfo(doc, msg, INVALID_MODULE_MARKER_TYPE,
IMarker.SEVERITY_WARNING, false, true, 0, 0, 0, 0, null);
ArrayList<MarkerInfo> lst = new ArrayList<MarkerInfo>();
lst.add(markerInfo);
PydevMarkerUtils.replaceMarkers(lst, fileAdapter, INVALID_MODULE_MARKER_TYPE, true, new NullProgressMonitor());
}
/**
* When we have the editor input re-set, we have to change the parser and the partition scanner to
* the new document. This happens in 3 cases:
* - when the editor has been created
* - when the editor is reused in the search window
* - when we create a file, and make a save as, to change its name
*
* there were related bugs in each of these cases:
* https://sourceforge.net/tracker/?func=detail&atid=577329&aid=1250307&group_id=85796
* https://sourceforge.net/tracker/?func=detail&atid=577329&aid=1251271&group_id=85796
*
* @see org.eclipse.ui.texteditor.AbstractTextEditor#doSetInput(org.eclipse.ui.IEditorInput)
*/
@Override
protected void doSetInput(IEditorInput input) throws CoreException {
//Having a new input is treated as opening a new editor for the ping.
if (!Platform.inDevelopmentMode() || ILogPing.FORCE_SEND_WHEN_IN_DEV_MODE) {
ILogPing logPing = PydevPlugin.getAsyncLogPing();
logPing.addPingOpenEditor();
}
IEditorInput oldInput = this.getEditorInput();
//Remove markers from the old
if (oldInput != null) {
IFile oldFile = (IFile) oldInput.getAdapter(IFile.class);
if (oldFile != null) {
removeInvalidModuleMarkers(oldFile);
}
}
synchronized (lockHandle) {
releaseCurrentHandle();
}
super.doSetInput(input);
try {
IDocument document = getDocument(input);
if (input != null) {
IFile newFile = (IFile) input.getAdapter(IFile.class);
if (newFile != null) {
//Add invalid module name markers to the new.
checkAddInvalidModuleNameMarker(document, newFile);
}
//see if we have to change the encoding of the file on load
fixEncoding(input, document);
PyParserManager.getPyParserManager(PydevPrefs.getPreferences()).attachParserTo(this);
if (document != null) {
PyPartitionScanner.checkPartitionScanner(document);
}
}
notifier.notifyInputChanged(oldInput, input);
notifier.notifyOnSetDocument(document);
} catch (Throwable e) {
Log.log(e);
}
try {
PyEditTitle.invalidateTitle(this, input);
} catch (Throwable e) {
Log.log(e);
}
try {
if (this.isCythonFile()) {
this.setTitleImage(PydevPlugin.getImageCache().get(UIConstants.CYTHON_FILE_ICON));
}
} catch (Throwable e) {
Log.log(e);
}
}
/* default */void setEditorTitle(String title) {
setPartName(title);
firePropertyChange(PROP_DIRTY);
}
/* default */void setEditorImage(Image image) {
setTitleImage(image);
}
/**
* @return true if the editor passed as a parameter has the same input as this editor.
*/
public boolean hasSameInput(IPyEdit edit) {
IEditorInput thisInput = this.getEditorInput();
IEditorInput otherInput = edit.getEditorInput();
if (thisInput == null || otherInput == null) {
return false;
}
if (thisInput == otherInput || thisInput.equals(otherInput)) {
return true;
}
IResource r1 = (IResource) thisInput.getAdapter(IResource.class);
IResource r2 = (IResource) otherInput.getAdapter(IResource.class);
if (r1 == null || r2 == null) {
return false;
}
if (r1.equals(r2)) {
return true;
}
return false;
}
/**
* @param input the input from where we want to get the document
* @return the document for the passed input
*/
private IDocument getDocument(final IEditorInput input) {
return getDocumentProvider().getDocument(input);
}
/**
* @return the document that is binded to this editor (may be null)
*/
public IDocument getDocument() {
IDocumentProvider documentProvider = getDocumentProvider();
if (documentProvider != null) {
return documentProvider.getDocument(getEditorInput());
}
return null;
}
/**
* @see org.eclipse.ui.texteditor.AbstractTextEditor#performSave(boolean, org.eclipse.core.runtime.IProgressMonitor)
*/
protected void performSave(boolean overwrite, IProgressMonitor progressMonitor) {
final IDocument document = getDocument();
//Before saving, let's see if the auto-code formatting is turned on.
try {
//TODO CYTHON: support code-formatter.
if (PyCodeFormatterPage.getFormatBeforeSaving() && !isCythonFile()) {
IStatusLineManager statusLineManager = this.getStatusLineManager();
IDocumentProvider documentProvider = getDocumentProvider();
int[] regionsForSave = null;
if (PyCodeFormatterPage.getFormatOnlyChangedLines()) {
if (documentProvider instanceof PyDocumentProvider) {
PyDocumentProvider pyDocumentProvider = (PyDocumentProvider) documentProvider;
ITextFileBuffer fileBuffer = pyDocumentProvider.getFileBuffer(getEditorInput());
if (fileBuffer != null) {
regionsForSave = ChangedLinesComputer.calculateChangedLines(fileBuffer, progressMonitor);
}
} else {
Log.log("Was expecting PyDocumentProvider. Found: " + documentProvider);
}
}
ITextSelection selection = (ITextSelection) this.getSelectionProvider().getSelection();
PySelection ps = new PySelection(document, selection);
if (!hasSyntaxError(ps.getDoc())) {
PyFormatStd std = new PyFormatStd();
boolean throwSyntaxError = true;
try {
std.applyFormatAction(this, ps, regionsForSave, throwSyntaxError);
statusLineManager.setErrorMessage(null);
} catch (SyntaxErrorException e) {
statusLineManager.setErrorMessage(e.getMessage());
}
}
}
} catch (Throwable e) {
//can never fail
Log.log(e);
}
try {
fixEncoding(getEditorInput(), document);
} catch (Throwable e) {
//can never fail
Log.log(e);
}
super.performSave(overwrite, progressMonitor);
try {
PyParserManager.getPyParserManager(null).notifySaved(this);
notifier.notifyOnSave();
} catch (Throwable e) {
//can never fail
Log.log(e);
}
}
/**
* Checks if there's a syntax error at the document... if there is, returns false.
*
* Note: This function will also set the status line error message if there's an error message.
* Note: This function will actually do a parse operation when called (so, it should be called with care).
*/
public boolean hasSyntaxError(IDocument doc) throws MisconfigurationException {
Tuple<SimpleNode, Throwable> reparse = PyParser.reparseDocument(new PyParser.ParserInfo(doc, this, false));
if (reparse.o2 != null) {
this.getStatusLineManager().setErrorMessage(reparse.o2.getMessage());
return true;
}
return false;
}
/**
* Just to make it public.
*/
@Override
public void doSave(IProgressMonitor progressMonitor) {
super.doSave(progressMonitor);
}
/**
* Forces the encoding to the one specified in the file
*
* @param input
* @param document
*/
private void fixEncoding(final IEditorInput input, IDocument document) {
if (input instanceof FileEditorInput) {
final IFile file = (IFile) ((FileEditorInput) input).getAdapter(IFile.class);
try {
final String encoding = FileUtilsFileBuffer.getPythonFileEncoding(document, file.getFullPath().toOSString());
if (encoding != null) {
try {
if (encoding.equals(file.getCharset()) == false) {
new Job("Change encoding") {
protected IStatus run(IProgressMonitor monitor) {
try {
file.setCharset(encoding, monitor);
((TextFileDocumentProvider) getDocumentProvider()).setEncoding(input, encoding);
//refresh it...
file.refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException e) {
Log.log(e);
}
return Status.OK_STATUS;
}
}.schedule();
}
} catch (CoreException e) {
Log.log(e);
}
}
} catch (Exception e) {
Log.log(e);
}
}
}
/**
* @return the project for the file that's being edited (or null if not available)
*/
public IProject getProject() {
IEditorInput editorInput = this.getEditorInput();
if (editorInput instanceof FileEditorInput) {
IFile file = (IFile) ((FileEditorInput) editorInput).getAdapter(IFile.class);
return file.getProject();
}
return null;
}
/**
* @return the IFile being edited in this input (or null if not available)
*/
public IFile getIFile() {
IEditorInput editorInput = this.getEditorInput();
return (IFile) editorInput.getAdapter(IFile.class);
}
/**
* @return the File being edited
*/
public File getEditorFile() {
File f = null;
IEditorInput editorInput = this.getEditorInput();
IFile file = (IFile) editorInput.getAdapter(IFile.class);
if (file != null) {
IPath location = file.getLocation();
if (location != null) {
IPath path = location.makeAbsolute();
f = path.toFile();
}
} else if (editorInput instanceof PydevFileEditorInput) {
PydevFileEditorInput pyEditorInput = (PydevFileEditorInput) editorInput;
f = pyEditorInput.getPath().toFile();
} else {
try {
if (editorInput instanceof IURIEditorInput) {
IURIEditorInput iuriEditorInput = (IURIEditorInput) editorInput;
return new File(iuriEditorInput.getURI());
}
} catch (Throwable e) {
//OK, IURIEditorInput was only added on eclipse 3.3
}
try {
IPath path = (IPath) Reflection.invoke(editorInput, "getPath", new Object[0]);
f = path.toFile();
} catch (Throwable e) {
//ok, it has no getPath
}
}
return f;
}
// cleanup
public void dispose() {
synchronized (lockHandle) {
releaseCurrentHandle();
}
if (!this.disposed) {
this.disposed = true;
synchronized (currentlyOpenedEditorsLock) {
currentlyOpenedEditors.remove(this);
}
try {
IFile iFile = this.getIFile();
if (iFile != null) {
removeInvalidModuleMarkers(iFile);
}
} catch (Throwable e1) {
Log.log(e1);
}
try {
this.onDispose.call(null);
notifier.notifyOnDispose();
PydevPrefs.getChainedPrefStore().removePropertyChangeListener(prefListener);
PyParserManager.getPyParserManager(null).notifyEditorDisposed(this);
colorCache.dispose();
pyEditScripting = null;
cache.clear();
cache = null;
if (this.resourceManager != null) {
this.resourceManager.dispose();
this.resourceManager = null;
}
synchronized (registeredEditListeners) {
registeredEditListeners.clear();
}
} catch (Throwable e) {
Log.log(e);
}
}
super.dispose();
}
public static class MyResources extends ListResourceBundle {
public Object[][] getContents() {
return contents;
}
static final Object[][] contents = { { "CorrectionAssist", "CorrectionAssist" },
{ "ContentAssistProposal", "ContentAssistProposal" }, { "TemplateProposals", "TemplateProposals" }, };
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.texteditor.AbstractTextEditor#createActions()
*
* TODO: Fix content assist to work in emacs mode:
* http://wiki.eclipse.org/index.php/FAQ_How_do_I_add_Content_Assist_to_my_editor%3F
* http://www.eclipse.org/newsportal/article.php?id=61744&group=eclipse.platform#61744
*/
protected void createActions() {
super.createActions();
try {
MyResources resources = new MyResources();
IAction action;
//Quick-Assist: it's added to the platform as of Eclipse 3.2, so, we do not have to put the binding here
// -------------------------------------------------------------------------------------
// This action will fire a CONTENTASSIST_PROPOSALS operation
// when executed
action = new ContentAssistAction(resources, "ContentAssistProposal.", this);
action.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
setAction("ContentAssistProposal", action);
markAsStateDependentAction("ContentAssistProposal", true);
// -------------------------------------------------------------------------------------
//open action
IAction openAction = new PyOpenAction();
setAction(ACTION_OPEN, openAction);
// -------------------------------------------------------------------------------------
// Offline action
action = new OfflineAction(resources, "Pyedit.ScriptEngine.", this);
action.setActionDefinitionId("org.python.pydev.editor.actions.scriptEngine");
action.setId("org.python.pydev.editor.actions.scriptEngine");
setAction("PyDevScriptEngine", action);
// -------------------------------------------------------------------------------------
//move lines
if (this.getIndentPrefs().getSmartLineMove()) {
//Don't even bind the action if the smart line move is not set.
//This means 2 things:
//- Uses the default action when asked
//- An editor restart will be needed to have it applied
action = new PyMoveLineUpAction(resources, "Pyedit.MoveLinesUp.", this);
action.setActionDefinitionId(ITextEditorActionDefinitionIds.MOVE_LINES_UP);
action.setId("org.python.pydev.editor.actions.moveLineUp");
setAction(ITextEditorActionConstants.MOVE_LINE_UP, action);
action = new PyMoveLineDownAction(resources, "Pyedit.MoveLinesDown.", this);
action.setActionDefinitionId(ITextEditorActionDefinitionIds.MOVE_LINES_DOWN);
action.setId("org.python.pydev.editor.actions.moveLineDown");
setAction(ITextEditorActionConstants.MOVE_LINE_DOWN, action);
}
notifier.notifyOnCreateActions(resources);
onCreateActions.call(this);
} catch (Throwable e) {
Log.log(e);
}
}
protected void initializeKeyBindingScopes() {
setKeyBindingScopes(new String[] { "org.python.pydev.ui.editor.scope" }); //$NON-NLS-1$
}
/**
* Used to: request a reparse / add listener / remove listener
* @return the parser that is being used in this editor.
*/
public PyParser getParser() {
return (PyParser) PyParserManager.getPyParserManager(null).getParser(this);
}
/**
* Returns the status line manager of this editor.
* @return the status line manager of this editor
* @since 2.0
*
* copied from superclass, as it is private there...
*/
public IStatusLineManager getStatusLineManager() {
IEditorActionBarContributor contributor = getEditorSite().getActionBarContributor();
if (!(contributor instanceof EditorActionBarContributor))
return null;
IActionBars actionBars = ((EditorActionBarContributor) contributor).getActionBars();
if (actionBars == null)
return null;
return actionBars.getStatusLineManager();
}
/**
* This is the 'offline' action
*/
protected OfflineActionTarget fOfflineActionTarget;
/**
* @return an outline view
*/
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
if (OfflineActionTarget.class.equals(adapter)) {
if (fOfflineActionTarget == null) {
IStatusLineManager manager = getStatusLineManager();
if (manager != null)
fOfflineActionTarget = (getSourceViewer() == null ? null : new OfflineActionTarget(
getSourceViewer(), manager, this));
}
return fOfflineActionTarget;
}
if (ICodeScannerKeywords.class.equals(adapter)) {
return new PyEditBasedCodeScannerKeywords(this);
}
if (IContentOutlinePage.class.equals(adapter)) {
return new PyOutlinePage(this);
} else {
Object adaptable = this.onGetAdapter.call(adapter);
if (adaptable != null) {
return adaptable;
}
return super.getAdapter(adapter);
}
}
/**
* implementation copied from org.eclipse.ui.externaltools.internal.ant.editor.PlantyEditor#setSelection
*/
public void setSelection(int offset, int length) {
ISourceViewer sourceViewer = getSourceViewer();
sourceViewer.setSelectedRange(offset, length);
sourceViewer.revealRange(offset, length);
}
/**
* Selects more than one node, making a selection from the 1st node to the last node passed.
*/
public void revealModelNodes(SimpleNode[] nodes) {
if (nodes == null) {
return; // nothing to see here
}
IDocument document = getDocumentProvider().getDocument(getEditorInput());
if (document == null) {
return;
}
try {
int startOffset = -1, endOffset = -1;
PySelection selection = new PySelection(this);
for (SimpleNode node : nodes) {
int nodeStartoffset = selection.getLineOffset(node.beginLine - 1) + node.beginColumn - 1;
int[] colLineEnd = NodeUtils.getColLineEnd(node);
int nodeEndOffset = selection.getLineOffset(colLineEnd[0] - 1) + colLineEnd[1] - 1;
if (startOffset == -1 || nodeStartoffset < startOffset) {
startOffset = nodeStartoffset;
}
if (endOffset == -1 || nodeEndOffset > endOffset) {
endOffset = nodeEndOffset;
}
}
setSelection(startOffset, endOffset - startOffset);
} catch (Exception e) {
Log.log(e);
}
}
/**
* Shows some node in the editor.
* @param node the node to be shown.
*/
public void revealModelNode(SimpleNode node) {
if (node == null) {
return; // nothing to see here
}
IDocument document = getDocumentProvider().getDocument(getEditorInput());
if (document == null) {
return;
}
int offset, length, endOffset;
try {
PySelection selection = new PySelection(this);
offset = selection.getLineOffset(node.beginLine - 1) + node.beginColumn - 1;
int[] colLineEnd = NodeUtils.getColLineEnd(node);
endOffset = selection.getLineOffset(colLineEnd[0] - 1) + colLineEnd[1] - 1;
length = endOffset - offset;
setSelection(offset, length);
} catch (Exception e) {
Log.log(e);
}
}
private Tuple3<Integer, IModulesManager, String> handle;
private final Object lockHandle = new Object();
/**
* Note that the lockHandle must be already synched before this method is called.
*/
private void releaseCurrentHandle() {
if (this.handle != null) {
this.handle.o2.popTemporaryModule(this.handle.o3, this.handle.o1);
this.handle = null;
}
}
/**
* This event comes when document was parsed (with or without errors)
*
* Removes all the error markers
*/
public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc) {
this.errorDescription = null; //the order is: parserChanged and only then parserError
ast = (SimpleNode) root;
astModificationTimeStamp = ((IDocumentExtension4) doc).getModificationStamp();
try {
IPythonNature pythonNature = this.getPythonNature();
if (pythonNature != null) {
ICodeCompletionASTManager astManager = pythonNature.getAstManager();
if (astManager != null) {
IModulesManager modulesManager = astManager.getModulesManager();
if (modulesManager != null) {
File editorFile = this.getEditorFile();
if (editorFile != null) {
String moduleName = pythonNature.resolveModule(editorFile);
if (moduleName != null) {
synchronized (lockHandle) {
releaseCurrentHandle();
int modHandle = modulesManager.pushTemporaryModule(moduleName, new SourceModule(
moduleName, editorFile, ast, null));
this.handle = new Tuple3<Integer, IModulesManager, String>(modHandle,
modulesManager, moduleName);
}
}
}
}
}
}
} catch (MisconfigurationException e) {
Log.log(e);
}
fireModelChanged(ast);
//Trying to fix issue where it seems that the text presentation is not properly updated after markers are
//changed (i.e.: red lines remain there when they shouldn't).
//I couldn't really reproduce this issue, so, this may not fix it...
//
//Details: https://sourceforge.net/projects/pydev/forums/forum/293649/topic/4477776
// RunInUiThread.async(new Runnable() {
//
// public void run() {
// if(!isDisposed()){
// getSourceViewer().invalidateTextPresentation();
// }
// }
// });
}
/**
* This event comes when parse ended in an error
*
* Generates an error marker on the document
*/
public void parserError(Throwable error, IAdaptable original, IDocument doc) {
ErrorDescription errDesc = null;
try {
errDesc = PyParser.createParserErrorMarkers(error, original, doc);
} catch (CoreException e1) {
// Whatever, could not create a marker. Swallow this one
Log.log(e1);
} catch (BadLocationException e2) {
// Whatever, could not create a marker. Swallow this one
//PydevPlugin.log(e2);
} finally {
try {
errorDescription = errDesc;
fireParseErrorChanged(errorDescription);
} catch (Exception e) {
Log.log(e);
}
}
}
/** stock listener implementation */
public void addModelListener(IModelListener listener) {
Assert.isNotNull(listener);
if (!modelListeners.contains(listener)) {
modelListeners.add(listener);
}
}
/** stock listener implementation */
public void removeModelListener(IModelListener listener) {
Assert.isNotNull(listener);
modelListeners.remove(listener);
}
/**
* stock listener implementation event is fired whenever we get a new root
*/
protected void fireModelChanged(SimpleNode root) {
//create a copy, to avoid concurrent modifications
for (IModelListener listener : new ArrayList<IModelListener>(modelListeners)) {
try {
listener.modelChanged(root);
} catch (Exception e) {
Log.log(e);
}
}
}
/**
* @return the last ast generated in this editor (even if we had some other error after that)
*/
public SimpleNode getAST() {
return ast;
}
public long getAstModificationTimeStamp() {
return astModificationTimeStamp;
}
/**
* @return a list of tuples, where the 1st element in the tuple is a String and the 2nd element is
* an icon, ordered so that the 1st item is the topmost and the last is the innermost.
*/
public List<String[]> getInnerStructureFromLine(int line) {
ArrayList<String[]> ret = new ArrayList<String[]>();
List<stmtType> parseToKnowGloballyAccessiblePath = FastParser.parseToKnowGloballyAccessiblePath(getDocument(),
line);
for (stmtType stmtType : parseToKnowGloballyAccessiblePath) {
String rep = NodeUtils.getRepresentationString(stmtType);
String image;
if (stmtType instanceof ClassDef) {
image = UIConstants.CLASS_ICON;
} else if (stmtType instanceof FunctionDef) {
image = UIConstants.METHOD_ICON;
} else {
image = UIConstants.ERROR;
}
ret.add(new String[] { rep, image });
}
return ret;
}
/**
* This function will open an editor given the passed parameters
*
* @param projectName
* @param path
* @param innerStructure
* @throws MisconfigurationException
*/
public static void openWithPathAndInnerStructure(String projectName, IPath path, List<String> innerStructure)
throws MisconfigurationException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject project = workspace.getRoot().getProject(projectName);
if (project != null) {
IFile file = project.getFile(path);
if (file != null) {
IEditorPart editor = PyOpenEditor.doOpenEditor(file);
if (editor instanceof PyEdit) {
PyEdit pyEdit = (PyEdit) editor;
IPythonNature nature = pyEdit.getPythonNature();
AbstractModule mod = AbstractModule.createModuleFromDoc(nature.resolveModule(file), file
.getLocation().toFile(), pyEdit.getDocument(), nature, false);
StringBuffer tok = new StringBuffer(80);
for (String s : innerStructure) {
if (tok.length() > 0) {
tok.append('.');
}
tok.append(s);
}
try {
IDefinition[] definitions = mod.findDefinition(CompletionStateFactory.getEmptyCompletionState(
tok.toString(), nature, new CompletionCache()), -1, -1, nature);
List<ItemPointer> pointers = new ArrayList<ItemPointer>();
PyRefactoringFindDefinition.getAsPointers(pointers, (Definition[]) definitions);
if (pointers.size() > 0) {
new PyOpenAction().run(pointers.get(0));
}
} catch (Exception e) {
Log.log(e);
}
}
}
}
}
/**
* @return the last error description found (may be null)
*/
public ErrorDescription getErrorDescription() {
return errorDescription;
}
/**
* stock listener implementation event is fired whenever the errors change in the editor
*/
private void fireParseErrorChanged(ErrorDescription errorDesc) {
for (IModelListener listener : new ArrayList<IModelListener>(modelListeners)) {
listener.errorChanged(errorDesc);
}
}
/**
* Only used if we weren't able
*/
public int getGrammarVersion() throws MisconfigurationException {
if (isCythonFile()) {
return IPythonNature.GRAMMAR_PYTHON_VERSION_CYTHON;
}
IPythonNature nature;
nature = getPythonNature();
if (nature != null) {
return nature.getGrammarVersion();
}
Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(getEditorFile());
return infoForFile.o1.getGrammarVersion();
}
public IGrammarVersionProvider getGrammarVersionProvider() {
return new IGrammarVersionProvider() {
public int getGrammarVersion() throws MisconfigurationException {
//Always calculate at the present time based on the editor configuration.
return PyEdit.this.getGrammarVersion();
}
};
}
public boolean isCythonFile() {
IFile iFile = getIFile();
String fileName = null;
if (iFile != null) {
fileName = iFile.getName();
} else {
File editorFile = getEditorFile();
if (editorFile != null) {
fileName = editorFile.getName();
}
}
return FileTypesPreferencesPage.isCythonFile(fileName);
}
/**
* @return the python nature associated with this editor.
* @throws NotConfiguredInterpreterException
*/
public IPythonNature getPythonNature() throws MisconfigurationException {
IProject project = getProject();
if (project == null || !project.isOpen()) {
return null;
}
IPythonNature pythonNature = PythonNature.getPythonNature(project);
if (pythonNature != null) {
return pythonNature;
}
//if it's an external file, there's the possibility that it won't be added even here.
pythonNature = PythonNature.addNature(this.getEditorInput());
if (pythonNature != null) {
return pythonNature;
}
Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(getEditorFile());
if (infoForFile == null) {
NotConfiguredInterpreterException e = new NotConfiguredInterpreterException();
ErrorDialog.openError(PyAction.getShell(), "Error: no interpreter configured",
"Interpreter not configured\n(Please, Configure it under window->preferences->PyDev)",
PydevPlugin.makeStatus(IStatus.ERROR, e.getMessage(), e));
throw e;
}
pythonNature = infoForFile.o1;
return pythonNature;
}
protected void initializeEditor() {
super.initializeEditor();
try {
this.setPreferenceStore(PydevPrefs.getChainedPrefStore());
setEditorContextMenuId(PY_EDIT_CONTEXT);
setDocumentProvider(PyDocumentProvider.instance);
} catch (Throwable e) {
Log.log(e);
}
}
//------------------------------------------------------------------- START: actions that are activated after Ctrl+2
OfflineActionsManager offlineActionsManager = new OfflineActionsManager();
public Collection<ActionInfo> getOfflineActionDescriptions() {
return offlineActionsManager.getOfflineActionDescriptions();
}
public void addOfflineActionListener(String key, IAction action) {
offlineActionsManager.addOfflineActionListener(key, action);
}
public void addOfflineActionListener(String key, IAction action, String description, boolean needsEnter) {
offlineActionsManager.addOfflineActionListener(key, action, description, needsEnter);
}
public boolean activatesAutomaticallyOn(String key) {
return offlineActionsManager.activatesAutomaticallyOn(key);
}
public boolean hasOfflineAction(String key) {
return offlineActionsManager.hasOfflineAction(key);
}
/**
* @return if an action was binded and was successfully executed
*/
public boolean onOfflineAction(String requestedStr, OfflineActionTarget target) {
return offlineActionsManager.onOfflineAction(requestedStr, target);
}
private LocalResourceManager resourceManager;
public synchronized LocalResourceManager getResourceManager() {
if (resourceManager == null) {
resourceManager = new LocalResourceManager(JFaceResources.getResources());
}
return resourceManager;
}
/**
* Used in the script pyedit_list_bindings.py
*/
public Font getFont(FontData descriptor) throws DeviceResourceException {
Font font = getResourceManager().createFont(FontDescriptor.createFrom(descriptor));
// Old implementation (for Eclipse 3.3)
// Font font = (Font) SWTResourceUtil.getFontTable().get(descriptor);
// if (font == null) {
// font = new Font(Display.getCurrent(), descriptor);
// SWTResourceUtil.getFontTable().put(descriptor, font);
// }
return font;
}
//--------------------------------------------------------------------- END: actions that are activated after Ctrl+2
public static void checkValidateState(IEditorPart iEditorPart) {
if (iEditorPart instanceof ITextEditorExtension2) {
ITextEditorExtension2 editor = (ITextEditorExtension2) iEditorPart;
editor.validateEditorInputState();
}
}
public static boolean isEditorOpenForResource(IResource r) {
synchronized (currentlyOpenedEditorsLock) {
for (PyEdit edit : currentlyOpenedEditors) {
IEditorInput input = edit.getEditorInput();
if (input != null) {
Object adapter = input.getAdapter(IResource.class);
if (adapter != null && r.equals(adapter)) {
return true;
}
}
}
}
return false;
}
}